<?php
/**
 * @author		João Batista Neto
 * @brief		Classes e interfaces relacionadas com o protocolo HTTP
 * @package		rpo.http
 */

require_once 'rpo/base/Object.php';
require_once 'rpo/http/HTTPBody.php';
require_once 'rpo/http/HTTPRequest.php';
require_once 'rpo/http/HTTPRequestMethod.php';

/**
 * @brief		Requisição HTTP de saída
 * @details		Implementação para uma solicitação de um cliente à um servidor.
 * @class		HTTPRequest
 */
class HTTPRequest extends Object {
	/**
	 * @brief	Corpo da requisição
	 * @var		HTTPBody
	 */
	private $body;

	/**
	 * @brief	Lista de cabeçalhos HTTP que serão enviados com a requisição
	 * @var		ArrayObject
	 */
	private $headers;

	/**
	 * @brief	Método de requisição HTTP
	 * @var		string
	 */
	private $method;

	/**
	 * @brief	Recurso a que se aplica a requisição
	 * @var		string
	 */
	private $uri;

	/**
	 * @brief	Constroi o objeto de requisição HTTP
	 * @param	$method string Método de requisição HTTP
	 * @param	$uri string Recurso a que se aplica a requisição
	 * @throws	InvalidArgumentException Se a URI não for válida ou se o método de requisição informado for desconhecido
	 * @throws	UnexpectedValueException Se o parâmetro $method não for uma string
	 */
	public function __construct( $method = 'GET' , $uri = null ){
		$this->headers = new ArrayObject();

		$this->setMethod( $method );

		if ( !is_null( $uri ) ){
			$this->setURI( $uri );
		}
	}

	/**
	 * @brief	Adiciona um cabeçalho HTTP à requisição
	 * @param	$header HTTPHeader
	 * @throws	LogicException Se o campo já tiver sido adicionado anteriormente
	 */
	public function addHeader( HTTPHeaderField $header ){
		$name = $header->getName();

		if ( $this->headers->offsetExists( $name ) ){
			throw new LogicException( sprintf( 'O campo de cabeçalho %s já foi adicionado.' , $name ) );
		}

		$this->headers[ $name ] = $header;
	}

	/**
	 * @brief	Recupera o corpo da requisição
	 * @return	HTTPBody
	 */
	public function getBody(){
		if ( is_null( $this->body ) ){
			$this->body = new HTTPBody();
		}

		return $this->body;
	}

	/**
	 * @brief	Recupera um Iterator para a lista de campos de cabeçalho
	 * @return	Iterator
	 */
	public function getHeaderIterator(){
		return $this->headers->getIterator();
	}

	/**
	 * @brief	Recupera o método de requisição HTTP
	 * @return	string
	 * @see		HTTPRequestMethod, OutgoingHTTPRequest::setMethod()
	 */
	public function getMethod(){
		return $this->method;
	}

	/**
	 * @brief	Recupera o recurso a que se aplica a requisição
	 * @return	string
	 * @see		OutgoingHTTPRequest::setURI()
	 */
	public function getURI(){
		return $this->uri;
	}

	/**
	 * @brief	Define o corpo da requisição
	 * @param	$body HTTPBody
	 */
	public function setBody( HTTPBody $body ){
		$this->body = $body;
	}

	/**
	 * @brief	Método de requisição HTTP
	 * @details	Define o método que será utilizado pela requisição, os métodos
	 * são sensíveis a caixa. A lista de métodos permitidos podem estar definidos
	 * no campo de cabeçalho Allow, informado pelo servidor.
	 * @param	$method string
	 * @see		HTTPRequestMethod
	 * @throws 	InvalidArgumentException Se o método de requisição informado for desconhecido
	 * @throws	UnexpectedValueException Se o parâmetro $method não for uma string
	 */
	public function setMethod( $method ){
		if ( is_string( $method ) ){
			switch ( $method ){
				case HTTPRequestMethod::DELETE:
				case HTTPRequestMethod::GET:
				case HTTPRequestMethod::HEAD:
				case HTTPRequestMethod::POST:
				case HTTPRequestMethod::PUT:
					$this->method = $method;
					break;
				default:
					throw new InvalidArgumentException( 'Método de requisição inválido.' );
			}
		} else {
			throw new UnexpectedValueException( 'O método de requisição deve ser uma string, conforme descrito em HTTPRequestMethod.' );
		}
	}

	/**
	 * @brief	Define o recurso a que se aplica a requisição
	 * @return	string
	 * @throws	InvalidArgumentException Se a URI informada não for válida
	 */
	public function setURI( $uri ){
		if ( filter_var( $uri , FILTER_VALIDATE_URL ) ){
			$this->uri = $uri;
		} else {
			throw new InvalidArgumentException( 'A URI informada não é válida.' );
		}
	}
}